from typing import Optional
from pydantic import BaseModel

from langchain_core.runnables import RunnableConfig

from .base import BaseNodes, tool_node
from ...models.graph import GraphState
from ...utils.str import unescape_html, replace_file_search_fix


class ReadFileSchema(BaseModel):
    path: str


class WriteFileSchema(BaseModel):
    path: str
    content: str


class ListFilesSchema(BaseModel):
    path: str
    recursive: Optional[bool] = False


class SearchFilesSchema(BaseModel):
    path: str
    regex: str
    file_pattern: Optional[str] = None


class ReplaceInFileSchema(BaseModel):
    path: str
    diff: str


class DownloadResourceSchema(BaseModel):
    resource_name: str
    resource_description: str
    path: str


class FileNodes(BaseNodes):

    @tool_node(args_schema=ReadFileSchema)
    def read_file(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
       Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.
       Parameters:
       - path: (required) The path of the file to read (relative to the current working directory ${cwd.toPosix()})
       Usage:
       <read_file>
       <path>File path here</path>
       </read_file>

       Example:
       <read_file>
       <path>src/index.js</path>
       </read_file>
       """
        return self.wait_human_feedback()

    @tool_node(args_schema=WriteFileSchema, args_parser={"content": unescape_html})
    def write_to_file(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        Description: Request to write content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
        Parameters:
        - path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
        - content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.
        Usage:
        <write_to_file>
        <path>File path here</path>
        <content>
            Your file content here
        </content>
        </write_to_file>

        ## Example 2: Requesting to create a new file

        <write_to_file>
        <path>src/frontend-config.json</path>
        <content>
        {
          "apiEndpoint": "https://api.example.com",
          "theme": {
            "primaryColor": "#007bff",
            "secondaryColor": "#6c757d",
            "fontFamily": "Arial, sans-serif"
          }
        }
        </content>
        </write_to_file>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=ListFilesSchema)
    def list_files(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
        Parameters:
        - path: (required) The path of the directory to list contents for (relative to the current working directory ${cwd.toPosix()})
        - recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only.
        Usage:
        <list_files>
        <path>Directory path here</path>
        <recursive>true or false (optional)</recursive>
        </list_files>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=SearchFilesSchema)
    def search_files(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
        Parameters:
        - path: (required) The path of the directory to search in (relative to the current working directory ${cwd.toPosix()}). This directory will be recursively searched.
        - regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
        - file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
        Usage:
        <search_files>
        <path>Directory path here</path>
        <regex>Your regex pattern here</regex>
        <file_pattern>file pattern here (optional)</file_pattern>
        </search_files>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=ReplaceInFileSchema, args_parser={"diff": replace_file_search_fix})
    def replace_in_file(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        描述: 请求使用SEARCH/REPLACE块替换现有文件中的内容，定义对文件特定部分的精确更改。当需要对文件的特定部分进行有针对性的更改时，应使用此工具。
        参数:
        - path: (必需) 要修改的文件路径(相对于当前工作目录${cwd.toPosix()})
        - diff: (必需) 一个或多个按照以下格式的SEARCH/REPLACE块:
          ```
          <<<<<<< SEARCH
          [要查找的精确内容]
          =======
          [要替换的新内容]
          >>>>>>> REPLACE
          ```
          重要规则:
          1. SEARCH内容必须与要查找的文件部分完全匹配:
             * 包括空格、缩进、换行符在内的逐字符匹配
             * 包含所有注释、文档字符串等
          2. SEARCH/REPLACE块只会替换第一个匹配项:
             * 如果需要进行多处更改，请包含多个唯一的SEARCH/REPLACE块
             * 在每个SEARCH部分中包含足够的行以唯一匹配需要更改的每组行
             * 使用多个SEARCH/REPLACE块时，按照它们在文件中出现的顺序列出
          3. 保持SEARCH/REPLACE块简洁:
             * 将大的SEARCH/REPLACE块分解成一系列较小的块，每个块只更改文件的一小部分
             * 只包含更改的行，如果需要唯一性，可以包含几个周围的行
             * 不要在SEARCH/REPLACE块中包含长段未更改的行
             * 每行必须完整。切勿在行中间截断，因为这可能导致匹配失败
          4. 特殊操作:
             * 移动代码: 使用两个SEARCH/REPLACE块(一个删除原始内容 + 一个在新位置插入)
             * 删除代码: 使用空的REPLACE部分
          5. 所有对同一个文件的修改必须放在一个replace_in_file工具调用的一个diff参数中:
             * 不要对同一个文件多次调用replace_in_file工具
             * 使用多个SEARCH/REPLACE块来表示对文件的多处修改
             * 多个SEARCH/REPLACE块必须按照它们在文件中出现的顺序列出
        用法:
        <replace_in_file>
            <path>文件路径</path>
            <diff>
                SEARCH/REPLACE 搜索和替换块
                SEARCH/REPLACE 搜索和替换块
                SEARCH/REPLACE 搜索和替换块
            </diff>
        </replace_in_file>

        ## 示例: 请求对文件进行有针对性的编辑

        <replace_in_file>
        <path>src/components/App.tsx</path>
        <diff>
        <<<<<<< SEARCH
        import React from 'react';
        =======
        import React, { useState } from 'react';
        >>>>>>> REPLACE

        <<<<<<< SEARCH
        function handleSubmit() {
          saveData();
          setLoading(false);
        }

        =======
        >>>>>>> REPLACE

        <<<<<<< SEARCH
        return (
          <div>
        =======
        function handleSubmit() {
          saveData();
          setLoading(false);
        }

        return (
          <div>
        >>>>>>> REPLACE
        </diff>
        </replace_in_file>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=DownloadResourceSchema)
    def download_resource_to_file(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        Description: Download resources like images, audio, or video files to a specified file path. This tool helps save external resources to local storage. IMPORTANT: Only one resource can be downloaded per tool call.
        Parameters:
        - resource_name: (required) The name or URL of the resource to download
        - resource_description: (required) A description of the resource to help identify its content and type
        - path: (required) The target file path where the resource should be saved (relative to the current working directory)
        Usage:
        <download_resource_to_file>
          <resource_name>Resource name or URL here</resource_name>
          <resource_description>Description of the resource</resource_description>
          <path>Target file path</path>
        </download_resource_to_file>

        Example:
        <download_resource_to_file>
          <resource_name>https://example.com/image.jpg or image.jpg</resource_name>
          <resource_description>A high-resolution photo of a mountain landscape</resource_description>
          <path>assets/images/landscape.jpg</path>
        </download_resource_to_file>

        Note: Each tool call can only process one resource download at a time. For multiple resources, make separate tool calls.
        """
        return self.wait_human_feedback()
